package hy.service.impl;

import com.alibaba.fastjson.JSON;
import hy.beans.Product;
import hy.beans.Result;
import hy.dao.ProductDao;
import hy.service.ProductService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

  @Service
  @EnableAspectJAutoProxy(exposeProxy = true)//重要，把代理对象暴露出来
  public class ProductServiceImpl implements ProductService {
      @Autowired
      private StringRedisTemplate tmp;
      @Autowired
      ProductDao dao;
      ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
  
    public List<Product> getAllProducts() {
          List<Product> list;
          //products是redis中一Map,其key为product_id值，value为product对象的json字符串
          if (tmp.hasKey("products")) {//从redis中加载数据
              list = new ArrayList<>();
  
              List<String> pro_str = (List<String>) (Object) tmp.opsForHash().values("products");
              for (String s : pro_str) {
                  Product pr = JSON.parseObject(s, Product.class);
                  list.add(pr);
              }
          } else {//从mysql中加载数据
              list = dao.selectAllProduct();
              list.forEach(p->{
                  tmp.opsForHash().put("products", p.getId() + "", JSON.toJSONString(p));
              });
//              for (Product p : list) {
//                  tmp.opsForHash().put("products", p.getId() + "", JSON.toJSONString(p));
//              }
              tmp.expire("products", 60, TimeUnit.SECONDS);
          }
          return list;
      }
  
      public Result addCart(String phone, String productId) {
          Result r = new Result();
          if (StringUtils.isEmpty(phone)) {
              return r;
          }
          if (tmp.opsForHash().size("cart_" + phone) > 0) {
              if (tmp.opsForHash().hasKey("cart_" + phone, productId)) {
                  int number = Integer.parseInt((String) tmp.opsForHash().get("cart_" + phone, productId));
                  tmp.opsForHash().put("cart_" + phone, productId, String.valueOf(number + 1));
              } else {
                  tmp.opsForHash().put("cart_" + phone, productId, "1");
              }
          } else {
              tmp.opsForHash().put("cart_" + phone, productId, "1");
          }
          r.setSuccess(true);
          return r;
          //redis中hgetall cart_138
      }
  
      public String createOrder(String phone) throws Exception {
          if (StringUtils.isEmpty(phone)) {
              return "你没有登录";
          }
          Map<String, String> map_cart = (Map<String, String>) (Object) tmp.opsForHash().entries("cart_" + phone);
          System.out.println(phone + "的购物车:" + map_cart);
          if (map_cart == null || map_cart.size() == 0) {
              return "购物车为空";
          }
          //修改库存前先删除缓存，重新读取，降低redis跟mysql不一致概率
          tmp.delete("products");
  
          //减库存
//          try{
//              dao.lockTable();
//              transac(map_cart);
//          }finally {
//              dao.unLockTable();
//          }
          //获取当前类的代理对象
          ProductService service = (ProductService) AopContext.currentProxy();
          String rs = null;
          try {
              dao.lockTable();//先上表锁，防止并发时由于行锁引起的死锁
              rs = service.transac(map_cart);
          } finally {
              dao.unLockTable();
          }
  
  
          //清空缓存
          /*1.删除redis中的products?
          在修改期间，其他线程可能又读取了数据，并放到缓存中，为了一致性，再次删除
          2.为什么要延迟删除？
          A.删除缓存，准备修改mysql
          B读数据，缓存没有，mysql中读
          A.修改完毕，再删除缓存
          B.把从mysql读到的错误数据写入缓存，从而导致A最后一次删除缓存的效果失效，mysql与缓存不一致
          3.为什么启用线程？提高性能
          */
          pool.schedule(
                  () -> tmp.delete("products"),
                  1,
                  TimeUnit.SECONDS
          );
          tmp.delete("cart_" + phone);
  
          return rs;
      }



    //库存不足回滚(mysql中rollback)
      @Transactional(rollbackFor = Exception.class)
      public String transac(Map<String, String> map_cart) throws Exception {
          System.out.println("事务开启了");
          //根据购物车要购买的数量从mysql减库存
          Set<String> ids = map_cart.keySet();
          for (String id : ids) {
              Integer sal_num = Integer.valueOf(map_cart.get(id));
              //修改mysql库存
              if (dao.updateProductNumById(Integer.valueOf(id), sal_num) == 0) {
                  throw new Exception("库存不足");
              }
          }
          return "success";
      }
  }